﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
using System.ComponentModel;
using System.Collections;
using System.Data.Common;
using Aspfm.Main;
using System.Text.RegularExpressions;

namespace Aspfm.Dal
{
    [Description(@"SQL语句必须具有通用性。
    例:Oracle函数to_number('1'),SQLserver中不存在。
    则在Sqlserver中创建该函数。不允许代码中针对特定的数据库写特殊的SQL。
    不允许出TOP等关键字")]
    public abstract class IDbHelper
    {
        static object obj = new object();

        //select name,(select name from systypes where xusertype=scl.xusertype and xtype=scl.xtype) typename
//from syscolumns scl where id =(select id from 
        //sysobjects where name='D430400JS2007GJSYD' and  xtype='P' )

        //SELECT * FROM USER_ARGUMENTS where object_name=upper('pronewidnum') 
        private System.Diagnostics.Stopwatch sw = null;
        public string TransactionId;
        internal string connstr = "";
        internal IDbTransaction trans = null;
        internal IDbConnection conn = null;
        internal bool longConn = false;

        public abstract DataSourceType GetDbType();
        internal IDbConnection GetConn()
        {
            if (trans == null)
            {
                return GetNewConn();
            }
            return trans.Connection;
        }
        internal IDbCommand CreateCommand(IDbConnection _conn)
        {
            if (_conn is Oracle.ManagedDataAccess.Client.OracleConnection)
            {
                Oracle.ManagedDataAccess.Client.OracleCommand ocmd = ((Oracle.ManagedDataAccess.Client.OracleConnection)_conn).CreateCommand();
                ocmd.BindByName = true;
                return ocmd;
            }
            return _conn.CreateCommand();
        }
        internal abstract IDbConnection GetNewConn();
        internal abstract IDbDataAdapter GetDataAdapter();
        private void ConnectionOpen(IDbConnection dbConnection)
        {
            if (dbConnection != null && dbConnection.State != ConnectionState.Open)
            {
                dbConnection.Open();

            }
        }
        private void ConnectionClose(IDbConnection dbConnection)
        {
            if (dbConnection != null && trans == null && longConn==false)
            {
                try
                {
                    dbConnection.Close();
                    dbConnection.Dispose();
                }
                catch { }
            }
        }
        public void LongConnBegin()
        {
            longConn = true;
            if (conn == null)
            {
                conn = GetConn();
                conn.Open();
            }
        }
        public void LongConnEnd()
        {
            longConn = false;
            ConnectionClose(conn);
        }
        public void TransactionBegin(IsolationLevel IsolationLevel = IsolationLevel.ReadCommitted)
        { 
             if (trans == null && conn==null)
             {
                 conn = GetConn();
                 conn.Open();
                 trans = conn.BeginTransaction(IsolationLevel);
                 TransactionId = Guid.NewGuid().ToString().ToUpper();
                 Loger.WriteLog(LogType.SQLLog, "开启事务..." + TransactionId);
             }
        }
        public void TransactionClose(bool isCommit = true)
        {
            if (trans != null && trans.Connection != null)
            {
                if (trans.Connection.State == ConnectionState.Open)
                {
                    if (isCommit)
                    {
                        trans.Commit();
                        Loger.WriteLog(LogType.SQLLog, "提交事务..." + TransactionId);
                    }
                    else
                    {
                        trans.Rollback();
                        Loger.WriteLog(LogType.SQLLog, "回滚事务..." + TransactionId);
                    }
                    conn.Close();
                    conn.Dispose();
                    trans.Dispose();
                    trans = null;
                    conn = null;
                    TransactionId = "";
                    longConn = false;
                }
            }
        }
        internal string GetDataTypeByColumnName(string tableName, string columnName,DataSet DataColType = null)
        {
            tableName = tableName.Trim().ToUpper();
            if (DataColType == null)
            {
                DataColType = LaodAllColType(tableName);
            }
            if (DbFactory.CheckDSHadRow(DataColType))
            {
                DataRow[] drs = DataColType.Tables[0].Select("column_name='" + columnName.Trim().ToUpper() + "'");
                if (drs != null && drs.Length > 0)
                {
                    return drs[0]["data_type"].ToString();
                }
            }
            return "varchar";
        }
        internal abstract DataSet LaodAllColType(string tableName, string columnName = null);
        public abstract DataSet LaodProcParamsType(string procName);
        public abstract DataSet LaodProcAll();
        internal abstract string GetParamsName(string procName);

        public DataSet ExecProcParams(string procName, params object[] dbparameter)
        {
            if (dbparameter != null)
            {
                ExecProc(procName, dbparameter.ToList());
            }
            return ExecProc(procName, null);
        }
        public DataSet ExecProc(string procName,List<object> dbparameter)
        {
            Exception ex = null;
            DataSet ds = null;
            if (!string.IsNullOrEmpty(procName))
            {
                string logMsg = "存储过程：" + procName + "  参数：";
                StartDate();
                List<IDataParameter> _dbparameter = new List<IDataParameter>();
                IDbConnection dbConnection = GetConn();
                try
                {
                    DataSet DataParams = LaodProcParamsType(procName);
                    if (DbFactory.CheckDSHadRow(DataParams))
                    {
                        for (int i = 0; i < DataParams.Tables[0].Rows.Count; i++)
                        {
                            object value = null;
                            if (dbparameter != null && dbparameter.Count > i)
                            {
                                value = dbparameter[i];
                            }
                            ParameterDirection paramDir = ParameterDirection.Input;
                            if (DataParams.Tables[0].Rows[i]["isoutparam"].ToString() == "1" ||
                                DataParams.Tables[0].Rows[i]["isoutparam"].ToString().Contains("OUT"))
                            {
                                paramDir = ParameterDirection.InputOutput;
                            }
                            string key = DataParams.Tables[0].Rows[i]["argument_name"].ToString();
                            string coltype = DataParams.Tables[0].Rows[i]["data_type"].ToString();
                            int length = 0;
                            int.TryParse(DataParams.Tables[0].Rows[i]["length"].ToString(), out length);
                            IDataParameter idata = ParamInit(key, value, coltype, paramDir, length);
                            _dbparameter.Add(idata);
                        }
                    }
                    using (IDbCommand dbCommand = CreateCommand(dbConnection))
                    {
                        dbCommand.CommandType = CommandType.StoredProcedure;
                        //dbCommand.BindByName = BindByName;
                        dbCommand.CommandText = procName.ToUpper();
                        if (_dbparameter != null && _dbparameter.Count > 0)
                        {
                            for (int i = 0; i < _dbparameter.Count; i++)
                            {
                                logMsg += "[" + _dbparameter[i].ParameterName + " ：" + (_dbparameter[i].Value == null ? "" : _dbparameter[i].Value.ToString()) + " type：" + _dbparameter[i].DbType.ToString() + "]";
                                dbCommand.Parameters.Add(_dbparameter[i]);
                            }
                        }
                        ConnectionOpen(dbConnection);
                        dbCommand.Transaction = trans;

                        IDbDataAdapter da = GetDataAdapter();
                        da.SelectCommand = dbCommand;
                        ds = new DataSet();
                        DataTable dt = ds.Tables.Add("procparams");
                        da.Fill(ds);
                        if (_dbparameter != null && _dbparameter.Count > 0)
                        {
                            dt.Rows.Add(dt.NewRow());
                            for (int i = 0; i < _dbparameter.Count; i++)
                            {
                                dt.Columns.Add(_dbparameter[i].ParameterName.ToUpper());
                                object dbvalue = _dbparameter[i].Value;
                                dt.Rows[0][_dbparameter[i].ParameterName.ToUpper()] = dbvalue;
                            }
                        }
                    }
                }
                catch (Exception _ex)
                {
                    TransactionClose(false);
                    ex = _ex;
                }
                finally
                {
                    ConnectionClose(dbConnection);
                }
                StopDate(logMsg, ex);
            }
            return ds;
        }
        public abstract bool ExistTableOrView(string tableName);
        public bool ExistColumn(string tableName, string columnName)
        {
            System.Data.DataSet dataSet = QuerySql("select * from " + tableName + " where 1=0");
            if (dataSet != null && dataSet.Tables.Count > 0 && dataSet.Tables[0].Columns.Contains(columnName.Trim().ToUpper()))
            {
                return true;
            }
            return false;
        }
        public bool ExecSql(string sql, params string[] dbparameter)
        {
            List<IDataParameter> _dbparameter = new List<IDataParameter>();
            sql = createCustmparams(sql, dbparameter, _dbparameter);
            return ExecSqlFull(sql, _dbparameter.ToArray());
        }

        private string createCustmparams(string sql, string[] dbparameter, List<IDataParameter> _dbparameter)
        {
            if (!string.IsNullOrEmpty(sql))
            {
                if (dbparameter != null && dbparameter.Length > 0)
                {
                    for (int i = 0; i < dbparameter.Length; i++)
                    {
                        if (sql.Contains("st(" + i + ")"))
                        {
                            sql = createCustmParams(sql, "st", dbparameter, _dbparameter, i);
                        }
                        if (sql.Contains("int(" + i + ")"))
                        {
                            sql = createCustmParams(sql, "int", dbparameter, _dbparameter, i, "decimal");
                        }
                        if (sql.Contains("dt(" + i + ")"))
                        {
                            sql = createCustmParams(sql, "dt", dbparameter, _dbparameter, i, "date");
                        }
                    }
                }
                //该方式为SQL参数化简写模式。但很容易被破解。
                //例：string name="sdfsdf";
                //例：select * from table where name like st('"+name+"');   结果：select * from table where name like st('sdfsdf')  正常
                //如果被破解成:name="') or 1=1";  结果：  select * from table where name like st('') or 1=1  这就失去了参数化的意义。
                //估删除。
                //sql = createCustmParamsStr(sql, "st('", "')", _dbparameter);
                //sql = createCustmParamsStr(sql, "int('", "')", _dbparameter, "decimal");
                //sql = createCustmParamsStr(sql, "dt('", "')", _dbparameter, "date");
            }
            return sql;
        }

        private string createCustmParamsStr(string sql, string start, string endstr,List<IDataParameter> _dbparameter, string coltype = "VARCHAR2")
        {
            int stindex = sql.IndexOf(start);
            int stindexend = -1;
            if (stindex >= 0)
            {
                stindexend = sql.IndexOf(endstr, stindex);
            }
            while (stindex >= 0 && stindexend > stindex)
            {
                string strvalue = sql.Substring(stindex + start.Length, stindexend - stindex - start.Length);
                string paramsname = GetParamsName("aspfm" + _dbparameter.Count);
                sql = sql.Substring(0, stindex) + paramsname + sql.Substring(stindexend + endstr.Length);
                IDataParameter idata = ParamInit(paramsname, strvalue, coltype);
                _dbparameter.Add(idata);
                stindex = sql.IndexOf(start);
                if (stindex >= 0)
                {
                    stindexend = sql.IndexOf(endstr, stindex);
                }
            }
            return sql;
        }
        private string createCustmParams(string sql, string funcname, string[] dbparameter, List<IDataParameter> _dbparameter, int i, string coltype = "VARCHAR2")
        {
            string paramsname = GetParamsName("aspfm" + i);
            sql = sql.Replace(funcname + "(" + i + ")", paramsname);
            IDataParameter idata = ParamInit(paramsname, dbparameter[i], coltype);
            _dbparameter.Add(idata);
            return sql;
        }

        public bool ExecSqlFull(string sql, params IDataParameter[] dbparameter)
        {
            bool issuss = false;
            Exception ex = null;
            string logMsg = "没有SQL参数";
            if (!string.IsNullOrEmpty(sql))
            {
                StartDate();
                IDbConnection dbConnection = GetConn();
                try
                {
                    using (IDbCommand dbCommand = CreateCommand(dbConnection))
                    {
                        dbCommand.CommandText = sql;
                        if (dbparameter != null && dbparameter.Length > 0)
                        {
                            logMsg = "  SQL参数：";
                            for (int i = 0; i < dbparameter.Length; i++)
                            {
                                logMsg += "[" + dbparameter[i].ParameterName + " ：" + (dbparameter[i].Value == null ? "" : dbparameter[i].Value.ToString()) + " type：" + dbparameter[i].DbType.ToString() + "]";
                                IDataParameter pp = (IDataParameter)((ICloneable)dbparameter[i]).Clone();
                                dbCommand.Parameters.Add(pp);
                            }
                        }
                        ConnectionOpen(dbConnection);
                        dbCommand.Transaction = trans;
                        dbCommand.ExecuteNonQuery();
                        issuss = true;
                    }
                }
                catch (Exception _ex)
                {
                    TransactionClose(false);
                    ex = _ex;
                }
                finally
                {
                    ConnectionClose(dbConnection);
                }
                StopDate(sql + logMsg, ex);
            }
            return issuss;
        }
        public DataSet QuerySqlFull(string sql, params IDataParameter[] dbparameter)
        {
            Exception ex = null;
            DataSet ds = null;
            string logMsg = "没有SQL参数";
            //if (!string.IsNullOrEmpty(sql) && sql.Trim().StartsWith("select", StringComparison.CurrentCultureIgnoreCase))
            if(true)
            {
                StartDate();
                IDbConnection dbConnection = GetConn();
                try
                {
                    using (IDbCommand dbCommand = CreateCommand(dbConnection))
                    {
                        dbCommand.Transaction = trans;
                        dbCommand.CommandText = sql;
                        if (dbparameter != null && dbparameter.Length > 0)
                        {
                            logMsg = "  SQL参数：";
                            for (int i = 0; i < dbparameter.Length; i++)
                            {
                                logMsg += "[" + dbparameter[i].ParameterName + " ：" + (dbparameter[i].Value == null ? "" : dbparameter[i].Value.ToString()) + " type：" + dbparameter[i].DbType.ToString() + "]";
                                IDataParameter pp = (IDataParameter)((ICloneable)dbparameter[i]).Clone();
                                dbCommand.Parameters.Add(pp);
                            }
                        }
                        //ConnectionOpen(dbConnection);
                        //dbCommand.Transaction = trans;
                        //IDataReader read = dbCommand.ExecuteReader();
                        //ds = ConvertDataReaderToDataSet(read);

                        IDbDataAdapter da = GetDataAdapter();
                        da.SelectCommand = dbCommand;
                        if (ds == null) ds = new DataSet();
                        da.Fill(ds);
                    }
                }
                catch (Exception _ex)
                {
                    ex = _ex;
                }
                finally
                {
                    ConnectionClose(dbConnection);
                }
                StopDate(sql + logMsg, ex);
            }
            return ds;
        }

        public DataSet ConvertDataReaderToDataSet(IDataReader reader)
        {
            DataSet dataSet = new DataSet();
            do
            {
                // Create new data table
                DataTable schemaTable = reader.GetSchemaTable();
                DataTable dataTable = new DataTable();
                if (schemaTable != null)
                {
                    // A query returning records was executed 
                    for (int i = 0; i < schemaTable.Rows.Count; i++)
                    {
                        DataRow dataRow = schemaTable.Rows[i];
                        // Create a column name that is unique in the data table 
                        string columnName = (string)dataRow["ColumnName"]; //+ " // Add the column definition to the data table 
                        DataColumn column = new DataColumn(columnName, (Type)dataRow["DataType"]);
                        dataTable.Columns.Add(column);
                    }
                    dataSet.Tables.Add(dataTable);
                    // Fill the data table we just created
                    while (reader.Read())
                    {
                        DataRow dataRow = dataTable.NewRow();
                        for (int i = 0; i < reader.FieldCount; i++)
                        {
                            dataRow[i] = reader.GetValue(i);
                        }
                        dataTable.Rows.Add(dataRow);
                    }
                }
                else
                {
                    // No records were returned
                    DataColumn column = new DataColumn("RowsAffected");
                    dataTable.Columns.Add(column);
                    dataSet.Tables.Add(dataTable);
                    DataRow dataRow = dataTable.NewRow();
                    dataRow[0] = reader.RecordsAffected;
                    dataTable.Rows.Add(dataRow);
                }
            }
            while (reader.NextResult());
            reader.Close();
            return dataSet;
        }  
        public DataSet QuerySql(string sql, params string[] dbparameter)
        {
            List<IDataParameter> _dbparameter = new List<IDataParameter>();
            sql = createCustmparams(sql, dbparameter, _dbparameter);
            return QuerySqlFull(sql, _dbparameter.ToArray());
        }

         [Description(@"该方法是参数值类型与数据库存储的类型进行比较，以免出现类型错误")]
        public object CheckValueType(string dbtypestr, object value, string paramName)
        {
            switch (dbtypestr.ToLower())
            {
                case "decimal":
                case "single":
                case "int16":
                case "int32":
                case "int64":
                case "number":
                    if (!(value is decimal))
                    {
                        decimal tempdec = 0;
                        if (!decimal.TryParse(value.ToString(), out tempdec))
                        {
                            throw new Exception("字段：" + paramName + " 值：" + value.ToString() + " 不是数字。");
                        }
                        value = tempdec;
                    }
                    break;
                case "double":
                    if (!(value is double))
                    {
                        double tempdec = 0;
                        if (!double.TryParse(value.ToString(), out tempdec))
                        {
                            throw new Exception("字段：" + paramName + " 值：" + value.ToString() + " 不是double数字。");
                        }
                        value = tempdec;
                    }
                    break;
                case "date":
                case "datetime":
                case "timestamp":
                    if (!(value is DateTime))
                    {
                        DateTime tempdt = DateTime.MinValue;
                        if (!DateTime.TryParse(value.ToString(), out tempdt))
                        {
                            throw new Exception("字段：" + paramName + " 值：" + value.ToString() + " 不是时间格式。");
                        }
                        value = tempdt;
                    }
                    break;
                case "text":
                case "image":
                case "raw":
                case "bfile":
                case "byte":
                    if (!(value is byte[]))
                    {
                        throw new Exception("字段：" + paramName + " 值：" + value.ToString() + " 不是byte[]类型。");
                    }
                    break;
                case "char":
                case "varchar":
                case "varchar2":
                case "clob":
                    if (!(value is string))
                    {
                        value = value.ToString();
                    }
                    break;
            }
            return value;
        }

        public decimal QuerySqlCount(string sql,params string[] dbparameter)
        {
            DataSet dsexe = QuerySql("select count(0) from ("+sql+") t", dbparameter);
            return Aspfm.Dal.DbFactory.ConvertDecimal(Aspfm.Dal.DbFactory.GetFirstValue(dsexe));
        }
        public DataSet QueryPage(decimal pageindex, decimal pagesize, string sql, string orderby, params string[] dbparameter)
        {
            return QuerySql(QueryPageSql(pageindex, pagesize, sql, orderby), dbparameter);
        }
        public string QueryPageSql(decimal pageindex, decimal pagesize, string sql, string orderby)
        {
            DataSourceList dslist = ConfigTool.ReadConn();
            return dslist.GetPageSql(GetDbType(), sql, orderby, pageindex, pagesize);
        }
        public bool SaveData(string tableName, AspfmModel htValue, string PrimaryKeyCol, bool IsInsertAndUpdate = true)
        {
            if (htValue == null || htValue.Values == null || htValue.Values.Count <= 0 || string.IsNullOrEmpty(PrimaryKeyCol))
            {
                return false;
            }
            if (PrimaryKeyCol.Contains(","))
            {
                string[] strkey = PrimaryKeyCol.Split(',');
                foreach (string str in strkey)
                {
                    if (!htValue.Values.ContainsKey(str) || htValue.Values[str] == null || string.IsNullOrEmpty(htValue.Values[str].ToString().Trim()))
                    {
                        throw new Exception("更新表：" + tableName + " 主键：" + str + " 为空值，不允许保存。");
                    }
                }
            }
            else
            {
                if (!htValue.Values.ContainsKey(PrimaryKeyCol) || htValue.Values[PrimaryKeyCol] == null || string.IsNullOrEmpty(htValue.Values[PrimaryKeyCol].ToString().Trim()))
                {
                    throw new Exception("更新表：" + tableName + " 主键：" + PrimaryKeyCol + " 为空值，不允许保存。");
                }
            }
            string strKey = "";
            string strValue = "";
            string strUpdate = "";
            string querykey = "";
            List<IDataParameter> querydbparameter = new List<IDataParameter>();
            List<IDataParameter> dbparameter = new List<IDataParameter>();
            DataSet DataColType = LaodAllColType(tableName);
            foreach (string key in htValue.Values.Keys)
            {
                strUpdate += key+"=" + GetParamsName(key) + ",";
                strKey += key + ",";
                strValue += GetParamsName(key) + ",";
                string coltype=GetDataTypeByColumnName(tableName,key,DataColType);
                IDataParameter idata = ParamInit(GetParamsName(key), htValue.GetPropertyValue(key), coltype);
                dbparameter.Add(idata);

                if (("," + PrimaryKeyCol + ",").Contains("," + key + ","))
                {
                    querykey += key + "=" + GetParamsName(key) + " and ";
                    querydbparameter.Add(idata);
                }
            }
            strKey = strKey.Trim(',');
            strValue = strValue.Trim(',');
            strUpdate = strUpdate.Trim(',');
            querykey = querykey.Remove(querykey.Length-5);

            string strSql = "select * from " + tableName + " where " + querykey;
            DataSet dsHad = QuerySqlFull(strSql, querydbparameter.ToArray());
            if (DbFactory.CheckDSHadRow(dsHad))
            {
                if (IsInsertAndUpdate)
                {
                    strSql = string.Format("update {0} set {1} where {2}", tableName, strUpdate, querykey);
                    return ExecSqlFull(strSql, dbparameter.ToArray());
                }
                else
                {
                    Loger.WriteLog("表" + tableName + "数据已存在，且没有设置强制更新。SQL:" + strSql);
                    return true;
                }
            }
            else
            {
                strSql = string.Format("insert into {0} ({1}) values ({2})", tableName, strKey, strValue);
                return ExecSqlFull(strSql, dbparameter.ToArray());
            }
            return false;
        }
        internal abstract IDataParameter ParamInit(string paramName, object value, string colType = "VARCHAR2", ParameterDirection paramDir = ParameterDirection.Input, int length=200);

        internal void StartDate()
        {
            sw = new System.Diagnostics.Stopwatch();
            sw.Start();
        }
        internal void StopDate(string logMsg, Exception _ex)
        {
            if (sw != null)
            {
                sw.Stop();
                logMsg += "   {" + sw.ElapsedMilliseconds + "}毫秒";
            }
            if (_ex != null)
            {
                logMsg += "   报错：" + _ex.Message+ _ex.StackTrace;
            }
            if (_ex != null)
            {
                if ((_ex.Message == "Internal Error" && _ex.Source == "Oracle.ManagedDataAccess") || _ex.Message.Contains("索引超出了数组界限"))
                {
                    _ex = new Exception("数据库表发生了变化。请刷新一次。或者应用程序重启。");
                    lock (obj)
                    {
                        try
                        {
                            string tempfile = BaseAshx.CurrentPath(true) + "aspfmlog.txt";
                            System.IO.File.WriteAllText(tempfile, "【Oracle.ManagedDataAccess内部错误:数据库表发生了变化。请刷新一次。或者应用程序重启。】", Encoding.UTF8);
                        }
                        catch { }
                    }
                    logMsg += _ex.Message;
                }
                Loger.WriteLog(LogType.ErrLog, logMsg, "", false);
                throw _ex;
            }
            else
            {
                Loger.WriteLog(LogType.SQLLog, logMsg);
            }
        }

        [Description(@"获取所有表的，主键GUID(全球唯一)。
            //调用实例：insert into bb (aa) values((select * from newid));")]
        public string GetPkId()
        {
            return DateTime.Now.ToString("yyyyMMddHHmmss") + Guid.NewGuid().ToString().Substring(0, 8).ToUpper();
        }
        public decimal GetPkOrderId(string beforeStr,string LASTPKID)
        {
            decimal maxid = 1;

            TransactionBegin();
            DataSet ds = QuerySql("select * from ASPFM_PK_INT_MAX where beforestr=st(0)", beforeStr);
            if (ds != null && ds.Tables.Count > 0 && ds.Tables[0].Rows.Count > 0)
            {
                maxid = (decimal)ds.Tables[0].Rows[0]["MAXID"];
                if (!string.IsNullOrEmpty(LASTPKID) && ds.Tables[0].Rows[0]["LASTPKID"].ToString() == LASTPKID)
                {
                    return maxid;
                }
                else
                {
                    maxid = maxid + 1;
                    ExecSql("update ASPFM_PK_INT_MAX set maxid=int(0),lastpkid=st(1) where beforeStr=st(2)", (maxid).ToString(), LASTPKID, beforeStr);
                }
            }
            else
            {
                ExecSql("insert into ASPFM_PK_INT_MAX (beforestr,maxid,lastpkid) values(st(0),int(1),st(2))",beforeStr, (maxid).ToString(), LASTPKID);
            }
            TransactionClose(true);
            return maxid;
        }
    }
}
